﻿using Synthesize.Infrastructure.EFDataAccess;
using Synthesize.Infrastructure.Modules;
using Library.Common.Config;
using Library.Core.Application.IInfrastructure;
using Library.Core.Infrastructure.Common;
using Library.Core.Infrastructure.EntityFrameworkDataAccess;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EasyCaching.CSRedis;
using EasyCaching.Core.Configurations;
using Library.Common.Serialize;
using System.Text.Unicode;
using System.Text.Json;
using MessagePack.Resolvers;
using Library.Core.Domain.Jwt;

namespace SMS.HangFireApi.App_Start
{
    /// <summary>
    /// Startup配置文件
    /// </summary>
    public static class StartupConfigure
    {

        /// <summary>
        /// 将服务添加到容器
        /// </summary>
        /// <param name="services"></param>
        /// <param name="Configuration"></param>
        /// <param name="hostEnvironment"></param>
        public static void AdminConfigureServices(this IServiceCollection services, IConfiguration Configuration, IWebHostEnvironment hostEnvironment)
        {
            //注册HttpContextHelper类型服务
            services.AddHttpContextAccessor();

            #region SqlServer EF+ACP

            services.AddDbContextPool<SynthesizeContextReadWrite>(options =>
            {
                //options.UseNpgsql(Configuration.GetSection("modules:2:properties:DBConnectionString").Value);
                options.UseSqlServer(Configuration.GetSection("modules:2:properties:DBConnectionString").Value);
                if (hostEnvironment.IsDevelopment())
                {
                    //打印参数值
                    options.EnableSensitiveDataLogging(true);//启用显示输入参数值敏感数据
                }
            }, poolSize: 64);
            services.AddDbContextPool<SynthesizeContextReadOnly>(options =>
            {
                //options.UseNpgsql(Configuration.GetSection("modules:2:properties:DBConnectionString_ReadOnly").Value);
                options.UseSqlServer(Configuration.GetSection("modules:2:properties:DBConnectionString_ReadOnly").Value, providerOptions =>
                {
                    providerOptions.CommandTimeout(120);
                });
                if (hostEnvironment.IsDevelopment())
                {
                    //打印参数值
                    options.EnableSensitiveDataLogging(true);
                }
            }, poolSize: 64);
            services.AddScoped<IEFContext, SynthesizeContext>();

            services.RegisterCapSubscribe();
            services.AddCap(x =>
            {
                x.UseDashboard();
                x.UseEntityFramework<SynthesizeContextReadWrite>();
                //x.UseSqlServer(Configuration.GetSection("modules:2:properties:DBConnectionString").Value);
                x.UseRabbitMQ(options =>
                {
                    options.HostName = Configuration.GetSection("modules:2:properties:RabbitMQ_ConnectionString").Value;
                    options.UserName = Configuration.GetSection("modules:2:properties:RabbitMQ_UserName").Value;
                    options.Password = Configuration.GetSection("modules:2:properties:RabbitMQ_Pwd").Value;
                    options.Port = Configuration.GetValue<int>("modules:2:properties:RabbitMQ_Port");
                });
            });
            #endregion

            #region Filters
            services.AddControllers(options =>
            {
                //options.Filters.Add(typeof(DomainExceptionFilter));
                //options.Filters.Add(typeof(ValidateModelAttribute));
            })
             .AddJsonOptions(x =>
             {
                 x.JsonSerializerOptions.Converters.Add(new DateTimeConverter());
                 x.JsonSerializerOptions.Converters.Add(new DateTimeNullConverter());
                 x.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(UnicodeRanges.All);
                 x.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;

             })
             .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

            #endregion

            #region Redis EasyCaching 通过特性缓存

            services.AddEasyCaching(option =>
            {
                if (hostEnvironment.IsDevelopment())
                {
                    #region IsDevelopment
                    //使用redis缓存命名为mymsgpack
                    //redis 单机：
                    option.UseRedis(config =>
                        {
                            config.DBConfig.Endpoints.Add(new ServerEndPoint(Configuration.GetSection("modules:2:properties:Redis_ConnectionString").Value, Configuration.GetValue<int>("modules:2:properties:Redis_Port")));
                            config.SerializerName = "mymsgpack";
                        }
                        ,Configuration.GetSection("modules:2:properties:Redis_ProviderName").Value
                    )
                    //使用MessagePack替换BinaryFormatter
                    .WithMessagePack(x =>
                    {
                        x.EnableCustomResolver = true;
                        // due to api changed
                        x.CustomResolvers = CompositeResolver.Create(new MessagePack.IFormatterResolver[]
                        {
                               //This can solve DateTime time zone problem
                               NativeDateTimeResolver.Instance,
                               ContractlessStandardResolver.Instance
                        });
                    }, "mymsgpack");

                    #endregion
                }
                else
                {
                    #region prod
                    //redis 哨兵模式(k8s哨兵模式只能在k8s内网访问):
                    option.UseCSRedis(config =>
                    {
                        config.DBConfig = new CSRedisDBOptions
                        {
                            ConnectionStrings = new List<string>
                            {
                            Configuration.GetSection("modules:2:properties:Redis_Sentinel_Conn").Value
                            },
                            // the sentinels settings
                            Sentinels = new List<string>
                            (
                                Configuration.GetSection("modules:2:properties:Redis_Sentinels").Value.Split(',')
                            ),
                            // the read write setting for sentinel mode
                            ReadOnly = false
                        };
                    }
                    , Configuration.GetSection("modules:2:properties:Redis_ProviderName").Value
                    )
                     .WithMessagePack(x =>
                     {
                         x.EnableCustomResolver = true;
                         // due to api changed
                         x.CustomResolvers = CompositeResolver.Create(new MessagePack.IFormatterResolver[]
                         {
                            // This can solve DateTime time zone problem
                            NativeDateTimeResolver.Instance,
                            ContractlessStandardResolver.Instance
                         });
                     }, "mymsgpack")
                    ;

                    #endregion
                }
            });
            //EasyCaching通过特性缓存
            //services.ConfigureCastleInterceptor(options => options.CacheProviderName = Configuration.GetSection("modules:2:properties:Redis_ProviderName").Value);
            #endregion

            #region Swagger
            services.AddSwaggerGen(options =>
            {
                var swaggerInfo = ConfigHelper<List<Dictionary<string, object>>>.InitConfig("Config/swagger.json");

                var swaggerConfig = swaggerInfo.GetNodeInfo("SwaggerConfig");
                foreach (var item in swaggerConfig)
                {
                    options.SwaggerDoc(item["SwaggerDoc"].ToString(), new OpenApiInfo
                    {
                        Version = Configuration.GetSection("SwaggerDoc:Version").Value,
                        Title = Configuration.GetSection("SwaggerDoc:Title").Value,
                        Description = Configuration.GetSection("SwaggerDoc:Description").Value
                    });
                }
                Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.xml").ToList().ForEach(file =>
                {
                    options.IncludeXmlComments(file, true);
                });

                options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT授权(数据将在请求头中进行传递)直接在下面框中输入Bearer {auth_token}(注意两者之间是一个空格) \"",
                    Name = "Authorization",
                    In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
                    Type = SecuritySchemeType.ApiKey
                });
                options.AddSecurityRequirement(new OpenApiSecurityRequirement()
                 {
                     {
                         new OpenApiSecurityScheme
                         {
                             Reference = new OpenApiReference
                             {
                                 Type = ReferenceType.SecurityScheme,
                                 Id = "Bearer"
                             }
                         },
                         new string[] {}
                     }
                 });
            });
            #endregion

            #region Jwt token 身份验证
            services.AddScoped<ICurrentUserInfo, CurrentUserInfo>();
            //JWT授权注入
            services.AddSingleton<IJwtFactory, JwtFactory>();
            services.AddScoped<CustomJwtEvents>();
            var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
            var signingKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(jwtAppSettingOptions[nameof(JwtIssuerOptions.Secret)]));
            services.Configure<JwtIssuerOptions>(options =>
            {
                options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
                options.ValidFor = TimeSpan.FromSeconds(Convert.ToDouble(jwtAppSettingOptions[nameof(JwtIssuerOptions.ValidFor)])) ;
                options.RefreshValidFor= TimeSpan.FromDays(Convert.ToInt32(jwtAppSettingOptions[nameof(JwtIssuerOptions.RefreshValidFor)]));
                options.SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha384);
            });

            services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            }).AddJwtBearer(configureOptions =>
            {
                configureOptions.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
                configureOptions.EventsType = typeof(CustomJwtEvents);
                configureOptions.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,//控制在令牌期间是否验证颁发者
                    ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],//获取或设置表示将使用的有效颁发者的System.String检查令牌的颁发者。
                    ValidateAudience = false,//控制在标记期间是否验证访问群体验证。

                    ValidateIssuerSigningKey = true,//控制是否验证Microsoft.IdentityModel.Tokens.SecurityKey签名的securityToken被调用
                    IssuerSigningKey = signingKey,

                    RequireExpirationTime = false,//指示令牌是否必须是否具有“过期”值
                    ValidateLifetime = true,//控制是否在令牌期间验证生存期
                    ClockSkew = TimeSpan.Zero//获取或设置验证时间时要应用的时钟偏移。
                    //注意这是缓冲过期时间，总的有效时间等于这个时间加上jwt的过期时间，如果不配置，默认是5分钟
                };
                configureOptions.SaveToken = true;
            });
          
            #endregion

        }

        /// <summary>
        /// 配置http请求管道
        /// </summary>
        /// <param name="app"></param>
        /// <param name="env"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="applicationLifetime"></param>
        public static void AdminConfigure(this IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory, IHostApplicationLifetime applicationLifetime)
        {

            //使用异常记录页面
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                //app.UseHsts();
            }

            //ASP.NET Core 使用app.UseStaticFiles配置静态文件中间件，达到类似IIS中虚拟目录的效果，可访问位于Web根目录之外的文件
            app.UseStaticFiles();

            #region Swagger

            //启用中间件服务生成Swagger作为JSON终结点
            app.UseSwagger().UseSwaggerUI(options =>
            {
                var swaggerInfo = ConfigHelper<List<Dictionary<string, object>>>.InitConfig("Config/swagger.json");
                var swaggerConfig = swaggerInfo.GetNodeInfo("SwaggerConfig");
                foreach (var item in swaggerConfig)
                {
                    options.SwaggerEndpoint(item["JsonPath"].ToString(), item["Title"].ToString());
                }

                options.RoutePrefix = string.Empty;// "swagger";//这里主要是不需要再输入swagger这个默认前缀
                options.DefaultModelsExpandDepth(-1);//隐藏掉实体映射
            });

            #endregion

            app.UseRouting();
            //1.先开启认证
            app.UseAuthentication();
            //2.再开启授权
            app.UseAuthorization();

            //app.UseCors("CorsPolicy");//必须在UseMvc前面
            app.UseCors(builder => builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            applicationLifetime.ApplicationStopped.Register(() =>
            {
                //Log.CloseAndFlush();
            });

        }
    }
}
